home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2009 February / PCWFEB09.iso / Software / Linux / Kubuntu 8.10 / kubuntu-8.10-desktop-i386.iso / casper / filesystem.squashfs / usr / share / perl5 / Mail / Header.pm < prev    next >
Text File  |  2008-04-14  |  14KB  |  636 lines

  1. # Copyrights 1995-2008 by Mark Overmeer <perl@overmeer.net>.
  2. #  For other contributors see ChangeLog.
  3. # See the manual pages for details on the licensing terms.
  4. # Pod stripped from pm file by OODoc 1.04.
  5. package Mail::Header;
  6. use vars '$VERSION';
  7. $VERSION = '2.03';
  8.  
  9. use strict;
  10. use Carp;
  11.  
  12. my $MAIL_FROM = 'KEEP';
  13. my %HDR_LENGTHS = ();
  14.  
  15. # Pattern to match a RFC822 Field name ( Extract from RFC #822)
  16. #
  17. #     field       =  field-name ":" [ field-body ] CRLF
  18. #
  19. #     field-name  =  1*<any CHAR, excluding CTLs, SPACE, and ":">
  20. #
  21. #     CHAR        =  <any ASCII character>        ; (  0-177,  0.-127.)
  22. #     CTL         =  <any ASCII control           ; (  0- 37,  0.- 31.)
  23. #              character and DEL>          ; (    177,     127.)
  24. # I have included the trailing ':' in the field-name
  25. #
  26. our $FIELD_NAME = '[^\x00-\x1f\x7f-\xff :]+:';
  27.  
  28.  
  29. ##
  30. ## Private functions
  31. ##
  32.  
  33. sub _error { warn @_; () }
  34.  
  35. # tidy up internal hash table and list
  36.  
  37. sub _tidy_header
  38. {   my $self    = shift;
  39.     my $deleted = 0;
  40.  
  41.     for(my $i = 0 ; $i < @{$self->{mail_hdr_list}}; $i++)
  42.     {   next if defined $self->{mail_hdr_list}[$i];
  43.  
  44.         splice @{$self->{mail_hdr_list}}, $i, 1;
  45.         $deleted++;
  46.         $i--;
  47.     }
  48.  
  49.     if($deleted)
  50.     {   local $_;
  51.         my @del;
  52.  
  53.         while(my ($key,$ref) = each %{$self->{mail_hdr_hash}} )
  54.         {   push @del, $key
  55.            unless @$ref = grep { ref $_ && defined $$_ } @$ref;
  56.         }
  57.  
  58.         delete $self->{'mail_hdr_hash'}{$_} for @del;
  59.     }
  60. }
  61.  
  62. # fold the line to the given length
  63.  
  64. my %STRUCTURE = map { (lc $_ => undef) }
  65.   qw{ To Cc Bcc From Date Reply-To Sender
  66.       Resent-Date Resent-From Resent-Sender Resent-To Return-Path
  67.       list-help list-post list-unsubscribe Mailing-List
  68.       Received References Message-ID In-Reply-To
  69.       Content-Length Content-Type Content-Disposition
  70.       Delivered-To
  71.       Lines
  72.       MIME-Version
  73.       Precedence
  74.       Status
  75.     };
  76.  
  77. sub _fold_line
  78. {   my($ln,$maxlen) = @_;
  79.  
  80.     $maxlen = 20
  81.        if $maxlen < 20;
  82.  
  83.     my $max = int($maxlen - 5);         # 4 for leading spcs + 1 for [\,\;]
  84.     my $min = int($maxlen * 4 / 5) - 4;
  85.  
  86.     $_[0] =~ s/[\r\n]+//og;        # Remove new-lines
  87.     $_[0] =~ s/\s*\Z/\n/so;        # End line with a EOLN
  88.  
  89.     return if $_[0] =~ /^From\s/io;
  90.  
  91.     if(length($_[0]) > $maxlen)
  92.     {   if($_[0] =~ /^([-\w]+)/ && exists $STRUCTURE{ lc $1 } )
  93.         {   #Split the line up
  94.             # first bias towards splitting at a , or a ; >4/5 along the line
  95.             # next split a whitespace
  96.             # else we are looking at a single word and probably don't want to split
  97.             my $x = "";
  98.             $x .= "$1\n " while $_[0] =~
  99.                 s/^\s*
  100.                    ( [^"]{$min,$max} [,;]
  101.                    | [^"]{1,$max}    [,;\s]
  102.                    | [^\s"]*(?:"[^"]*"[ \t]?[^\s"]*)+\s
  103.                    ) //x;
  104.  
  105.             $x .= $_[0];
  106.             $_[0] = $x;
  107.             $_[0] =~ s/(\A\s+|[\t ]+\Z)//sog;
  108.             $_[0] =~ s/\s+\n/\n/sog;
  109.         }
  110.         else
  111.         {   $_[0] =~ s/(.{$min,$max})(\s)/$1\n$2/g;
  112.             $_[0] =~ s/\s*$/\n/s;
  113.         }
  114.     }
  115.  
  116.     $_[0] =~ s/\A(\S+)\n\s*(?=\S)/$1 /so; 
  117. }
  118.  
  119. # Tags are case-insensitive, but there is a (slightly) prefered construction
  120. # being all characters are lowercase except the first of each word. Also
  121. # if the word is an `acronym' then all characters are uppercase. We decide
  122. # a word is an acronym if it does not contain a vowel.
  123. # In general, this change of capitization is a bad idea, but it is in
  124. # the code for ages, and therefore probably crucial for existing
  125. # applications.
  126.  
  127. sub _tag_case
  128. {   my $tag = shift;
  129.     $tag =~ s/\:$//;
  130.     join '-'
  131.       , map { /^[b-df-hj-np-tv-z]+$|^(?:MIME|SWE|SOAP|LDAP|ID)$/i
  132.               ? uc($_) : ucfirst(lc($_))
  133.             } split m/\-/, $tag, -1;
  134. }
  135.  
  136. # format a complete line
  137. #  ensure line starts with the given tag
  138. #  ensure tag is correct case
  139. #  change the 'From ' tag as required
  140. #  fold the line
  141.  
  142. sub _fmt_line
  143. {   my ($self, $tag, $line, $modify) = @_;
  144.     $modify ||= $self->{mail_hdr_modify};
  145.     my $ctag = undef;
  146.  
  147.     ($tag) = $line =~ /^($FIELD_NAME|From )/oi
  148.         unless defined $tag;
  149.  
  150.     if(defined $tag && $tag =~ /^From /io && $self->{mail_hdr_mail_from} ne 'KEEP')
  151.     {   if($self->{mail_hdr_mail_from} eq 'COERCE')
  152.         {   $line =~ s/^From /Mail-From: /o;
  153.             $tag = "Mail-From:";
  154.         }
  155.         elsif($self->{mail_hdr_mail_from} eq 'IGNORE')
  156.         {   return ();
  157.         }
  158.         elsif($self->{mail_hdr_mail_from} eq 'ERROR')
  159.         {    return _error "unadorned 'From ' ignored: <$line>";
  160.         }
  161.     }
  162.  
  163.     if(defined $tag)
  164.     {   $tag  = _tag_case($ctag = $tag);
  165.         $ctag = $tag if $modify;
  166.         $ctag =~ s/([^ :])$/$1:/o if defined $ctag;
  167.     }
  168.  
  169.     defined $ctag && $ctag =~ /^($FIELD_NAME|From )/oi
  170.         or croak "Bad RFC822 field name '$tag'\n";
  171.  
  172.     # Ensure the line starts with tag
  173.     if(defined $ctag && ($modify || $line !~ /^\Q$ctag\E/i))
  174.     {   (my $xtag = $ctag) =~ s/\s*\Z//o;
  175.         $line =~ s/^(\Q$ctag\E)?\s*/$xtag /i;
  176.     }
  177.  
  178.     my $maxlen = $self->{mail_hdr_lengths}{$tag}
  179.               || $HDR_LENGTHS{$tag}
  180.               || $self->fold_length;
  181.  
  182.     _fold_line $line, $maxlen
  183.         if $modify && defined $maxlen;
  184.  
  185.     $line =~ s/\n*$/\n/so;
  186.     ($tag, $line);
  187. }
  188.  
  189. sub _insert
  190. {   my ($self, $tag, $line, $where) = @_;
  191.  
  192.     if($where < 0)
  193.     {   $where = @{$self->{mail_hdr_list}} + $where + 1;
  194.         $where = 0 if $where < 0;
  195.     }
  196.     elsif($where >= @{$self->{mail_hdr_list}})
  197.     {   $where = @{$self->{mail_hdr_list}};
  198.     }
  199.  
  200.     my $atend = $where == @{$self->{mail_hdr_list}};
  201.     splice @{$self->{mail_hdr_list}}, $where, 0, $line;
  202.  
  203.     $self->{mail_hdr_hash}{$tag} ||= [];
  204.     my $ref = \${$self->{mail_hdr_list}}[$where];
  205.  
  206.     my $def = $self->{mail_hdr_hash}{$tag};
  207.     if($def && $where)
  208.     {   if($atend) { push @$def, $ref }
  209.         else
  210.         {   my $i = 0;
  211.             foreach my $ln (@{$self->{mail_hdr_list}})
  212.             {   my $r = \$ln;
  213.                 last if $r == $ref;
  214.                 $i++ if $r == $def->[$i];
  215.             }
  216.             splice @$def, $i, 0, $ref;
  217.         }
  218.     }
  219.     else
  220.     {    unshift @$def, $ref;
  221.     }
  222. }
  223.  
  224.  
  225. sub new
  226. {   my $call  = shift;
  227.     my $class = ref($call) || $call;
  228.     my $arg   = @_ % 2 ? shift : undef;
  229.     my %opt   = @_;
  230.  
  231.     $opt{Modify} = delete $opt{Reformat}
  232.         unless exists $opt{Modify};
  233.  
  234.     my $self = bless
  235.       { mail_hdr_list     => []
  236.       , mail_hdr_hash     => {}
  237.       , mail_hdr_modify   => (delete $opt{Modify} || 0)
  238.       , mail_hdr_foldlen  => 79
  239.       , mail_hdr_lengths  => {}
  240.       }, $class;
  241.  
  242.     $self->mail_from( uc($opt{MailFrom} || $MAIL_FROM) );
  243.  
  244.     $self->fold_length($opt{FoldLength})
  245.         if exists $opt{FoldLength};
  246.  
  247.     if(!ref $arg)               {}
  248.     elsif(ref($arg) eq 'ARRAY') { $self->extract( [ @$arg ] ) }
  249.     elsif(defined fileno($arg)) { $self->read($arg) }
  250.  
  251.     $self;
  252. }
  253.  
  254.  
  255. sub dup
  256. {   my $self = shift;
  257.     my $dup  = ref($self)->new;
  258.  
  259.     %$dup    = %$self;
  260.     $dup->empty;      # rebuild tables
  261.  
  262.     $dup->{mail_hdr_list} = [ @{$self->{mail_hdr_list}} ];
  263.  
  264.     foreach my $ln ( @{$dup->{mail_hdr_list}} )
  265.     {    my $tag = _tag_case +($ln =~ /^($FIELD_NAME|From )/oi)[0];
  266.          push @{$dup->{mail_hdr_hash}{$tag}}, \$ln;
  267.     }
  268.  
  269.     $dup;
  270. }
  271.  
  272.  
  273. sub extract
  274. {   my ($self, $lines) = @_;
  275.     $self->empty;
  276.  
  277.     while(@$lines && $lines->[0] =~ /^($FIELD_NAME|From )/o)
  278.     {    my $tag  = $1;
  279.          my $line = shift @$lines;
  280.          $line   .= shift @$lines
  281.              while @$lines && $lines->[0] =~ /^[ \t]+/o;
  282.  
  283.          ($tag, $line) = _fmt_line $self, $tag, $line;
  284.  
  285.          _insert $self, $tag, $line, -1
  286.              if defined $line;
  287.     }
  288.  
  289.     shift @$lines
  290.         if @$lines && $lines->[0] =~ /^\s*$/o;
  291.  
  292.     $self;
  293. }
  294.  
  295.  
  296. sub read
  297. {   my ($self, $fd) = @_;
  298.  
  299.     $self->empty;
  300.  
  301.     my ($tag, $line);
  302.     my $ln = '';
  303.     while(1)
  304.     {   $ln = <$fd>;
  305.  
  306.         if(defined $ln && defined $line && $ln =~ /\A[ \t]+/o)
  307.         {   $line .= $ln;
  308.             next;
  309.         }
  310.  
  311.         if(defined $line)
  312.         {   ($tag, $line) = _fmt_line $self, $tag, $line;
  313.             _insert $self, $tag, $line, -1
  314.             if defined $line;
  315.         }
  316.  
  317.         defined $ln && $ln =~ /^($FIELD_NAME|From )/o
  318.             or last;
  319.  
  320.         ($tag, $line) = ($1, $ln);
  321.     }
  322.  
  323.     $self;
  324. }
  325.  
  326.  
  327. sub empty
  328. {   my $self = shift;
  329.     $self->{mail_hdr_list} = [];
  330.     $self->{mail_hdr_hash} = {};
  331.     $self;
  332. }
  333.  
  334.  
  335. sub header
  336. {   my $self = shift;
  337.  
  338.     $self->extract(@_)
  339.     if @_;
  340.  
  341.     $self->fold
  342.         if $self->{mail_hdr_modify};
  343.  
  344.     [ @{$self->{mail_hdr_list}} ];
  345. }
  346.  
  347.  
  348. ### text kept, for educational purpose... originates from 2000/03
  349. # This can probably be optimized. I didn't want to mess much around with
  350. # the internal implementation as for now...
  351. # -- Tobias Brox <tobix@cpan.org>
  352.  
  353. sub header_hashref
  354. {   my ($self, $hashref) = @_;
  355.  
  356.     while(my ($key, $value) = each %$hashref)
  357.     {   $self->add($key, $_) for ref $value ? @$value : $value;
  358.     }
  359.  
  360.     $self->fold
  361.         if $self->{mail_hdr_modify};
  362.  
  363.     defined wantarray  # MO, added minimal optimization
  364.         or return;
  365.  
  366.     +{ map { ($_ => [$self->get($_)] ) }   # MO: Eh?
  367.            keys %{$self->{mail_hdr_hash}}
  368.      }; 
  369. }
  370.  
  371.  
  372. sub modify
  373. {   my $self = shift;
  374.     my $old  = $self->{mail_hdr_modify};
  375.  
  376.     $self->{mail_hdr_modify} = 0 + shift
  377.     if @_;
  378.  
  379.     $old;
  380. }
  381.  
  382.  
  383. sub mail_from
  384. {   my $thing  = shift;
  385.     my $choice = uc shift;
  386.  
  387.     $choice =~ /^(IGNORE|ERROR|COERCE|KEEP)$/ 
  388.     or die "bad Mail-From choice: '$choice'";
  389.  
  390.     if(ref $thing) { $thing->{mail_hdr_mail_from} = $choice }
  391.     else           { $MAIL_FROM = $choice }
  392.  
  393.     $thing;
  394. }
  395.  
  396.  
  397. sub fold_length
  398. {   my $thing = shift;
  399.     my $old;
  400.  
  401.     if(@_ == 2)
  402.     {   my $tag = _tag_case shift;
  403.         my $len = shift;
  404.  
  405.         my $hash = ref $thing ? $thing->{mail_hdr_lengths} : \%HDR_LENGTHS;
  406.         $old     = $hash->{$tag};
  407.         $hash->{$tag} = $len > 20 ? $len : 20;
  408.     }
  409.     else
  410.     {   my $self = $thing;
  411.         my $len  = shift;
  412.         $old = $self->{mail_hdr_foldlen};
  413.  
  414.         if(defined $len)
  415.         {    $self->{mail_hdr_foldlen} = $len > 20 ? $len : 20;
  416.              $self->fold if $self->{mail_hdr_modify};
  417.         }
  418.     }
  419.  
  420.     $old;
  421. }
  422.  
  423.  
  424. sub fold
  425. {   my ($self, $maxlen) = @_;
  426.  
  427.     while(my ($tag, $list) = each %{$self->{mail_hdr_hash}})
  428.     {   my $len = $maxlen
  429.              || $self->{mail_hdr_lengths}{$tag}
  430.              || $HDR_LENGTHS{$tag}
  431.              || $self->fold_length;
  432.  
  433.         foreach my $ln (@$list)
  434.         {    _fold_line $$ln, $len
  435.                  if defined $ln;
  436.         }
  437.     }
  438.  
  439.     $self;
  440. }
  441.  
  442.  
  443. sub unfold
  444. {   my $self = shift;
  445.  
  446.     if(@_)
  447.     {   my $tag  = _tag_case shift;
  448.         my $list = $self->{mail_hdr_hash}{$tag}
  449.             or return $self;
  450.  
  451.         foreach my $ln (@$list)
  452.         {   $$ln =~ s/\r?\n\s+/ /sog
  453.                 if defined $ln && defined $$ln;
  454.         }
  455.  
  456.         return $self;
  457.     }
  458.  
  459.     while( my ($tag, $list) = each %{$self->{mail_hdr_hash}})
  460.     {   foreach my $ln (@$list)
  461.         {   $$ln =~ s/\r?\n\s+/ /sog
  462.             if defined $ln && defined $$ln;
  463.         }
  464.     }
  465.  
  466.     $self;
  467. }
  468.  
  469.  
  470. sub add
  471. {   my ($self, $tag, $text, $where) = @_;
  472.     ($tag, my $line) = _fmt_line $self, $tag, $text;
  473.  
  474.     defined $tag && defined $line
  475.         or return undef;
  476.  
  477.     defined $where
  478.         or $where = -1;
  479.  
  480.     _insert $self, $tag, $line, $where;
  481.  
  482.     $line =~ /^\S+\s(.*)/os;
  483.     $1;
  484. }
  485.  
  486.  
  487. sub replace
  488. {   my $self = shift;
  489.     my $idx  = @_ % 2 ? pop @_ : 0;
  490.  
  491.     my ($tag, $line);
  492.   TAG:
  493.     while(@_)
  494.     {   ($tag,$line) = _fmt_line $self, splice(@_,0,2);
  495.  
  496.         defined $tag && defined $line
  497.             or return undef;
  498.  
  499.         my $field = $self->{mail_hdr_hash}{$tag};
  500.         if($field && defined $field->[$idx])
  501.              { ${$field->[$idx]} = $line }
  502.         else { _insert $self, $tag, $line, -1 }
  503.     }
  504.  
  505.     $line =~ /^\S+\s*(.*)/os;
  506.     $1;
  507. }
  508.  
  509.  
  510. sub combine
  511. {   my $self = shift;
  512.     my $tag  = _tag_case shift;
  513.     my $with = shift || ' ';
  514.  
  515.     $tag =~ /^From /io && $self->{mail_hdr_mail_from} ne 'KEEP'
  516.         and return _error "unadorned 'From ' ignored";
  517.  
  518.     my $def = $self->{mail_hdr_hash}{$tag}
  519.         or return undef;
  520.  
  521.     return $def->[0]
  522.         if @$def <= 1;
  523.  
  524.     my @lines = $self->get($tag);
  525.     chomp @lines;
  526.  
  527.     my $line = (_fmt_line $self, $tag, join($with,@lines), 1)[1];
  528.  
  529.     $self->{mail_hdr_hash}{$tag} = [ \$line ];
  530.     $line;
  531. }
  532.  
  533.  
  534. sub get
  535. {   my $self = shift;
  536.     my $tag = _tag_case shift;
  537.     my $idx = shift;
  538.  
  539.     my $def = $self->{mail_hdr_hash}{$tag}
  540.         or return ();
  541.  
  542.     my $l = length $tag;
  543.     $l   += 1 if $tag !~ / $/o;
  544.  
  545.     if(defined $idx || !wantarray)
  546.     {    $idx ||= 0;
  547.          my $val = ${$def->[$idx]};
  548.          defined $val or return undef;
  549.  
  550.      $val = substr $val, $l;
  551.      $val =~ s/^\s+//;
  552.          return $val;
  553.     }
  554.  
  555.     map { my $tmp = substr $$_,$l; $tmp =~ s/^\s+//; $tmp } @$def;
  556. }
  557.  
  558.  
  559.  
  560. sub count
  561. {   my $self = shift;
  562.     my $tag  = _tag_case shift;
  563.     my $def  = $self->{mail_hdr_hash}{$tag};
  564.     defined $def ? scalar(@$def) : 0;
  565. }
  566.  
  567.  
  568.  
  569. sub delete
  570. {   my $self = shift;
  571.     my $tag  = _tag_case shift;
  572.     my $idx  = shift;
  573.     my @val;
  574.  
  575.     if(my $def = $self->{mail_hdr_hash}{$tag})
  576.     {   my $l = length $tag;
  577.         $l   += 2 if $tag !~ / $/;
  578.  
  579.         if(defined $idx)
  580.         {   if(defined $def->[$idx])
  581.             {   push @val, substr ${$def->[$idx]}, $l;
  582.                 undef ${$def->[$idx]};
  583.             }
  584.         }
  585.         else
  586.         {   @val = map {my $x = substr $$_,$l; undef $$_; $x } @$def;
  587.         }
  588.  
  589.         _tidy_header($self);
  590.     }
  591.  
  592.     @val;
  593. }
  594.  
  595.  
  596.  
  597. sub print
  598. {   my $self = shift;
  599.     my $fd   = shift || \*STDOUT;
  600.  
  601.     foreach my $ln (@{$self->{mail_hdr_list}})
  602.     {   defined $ln or next;
  603.         print $fd $ln or return 0;
  604.     }
  605.  
  606.     1;
  607. }
  608.  
  609.  
  610. sub as_string { join '', grep {defined} @{shift->{mail_hdr_list}} }
  611.  
  612.  
  613. sub tags { keys %{shift->{mail_hdr_hash}} }
  614.  
  615.  
  616. sub cleanup
  617. {   my $self = shift;
  618.     my $deleted = 0;
  619.  
  620.     foreach my $key (@_ ? @_ : keys %{$self->{mail_hdr_hash}})
  621.     {   my $fields = $self->{mail_hdr_hash}{$key};
  622.         foreach my $field (@$fields)
  623.         {   next if $$field =~ /^\S+\s+\S/s;
  624.             undef $$field;
  625.             $deleted++;
  626.         }
  627.     }
  628.  
  629.     _tidy_header $self
  630.         if $deleted;
  631.  
  632.     $self;  
  633. }
  634.  
  635. 1;
  636.